namespace Game {
    using UnityEngine;
    using System;
    using System.Collections.Generic;
    using System.Reflection;

    /// <summary>
	///		CSV file loader.
	/// </summary>
	public class CsvFile {

        /// <summary>
        ///		Load as list of data in given format.
        /// </summary>
        /// <typeparam name="T">Data type.</typeparam>
        /// <param name="file">Path of CSV file. Relative to Assets/_Game/Setting/.</param>
        /// <param name="header">Header line. Data before header line will be ignored.</param>
        /// <returns>List of data</returns>
        public static List<T> Load<T>(string file, int header = 0) where T : new() {
            List<T> ret = new List<T>();
            CsvFile csv = new CsvFile();

            if (!csv.Parse(file, header)) return ret;

            Type type = typeof(T);
            FieldInfo[] fields = type.GetFields();

            for (int i = 0; i < csv.Count; ++i) {
                T one = new T();
                Dictionary<string, string> record = csv.contents[i];

                for (int j = 0; j < fields.Length; ++j) {
                    FieldInfo field = fields[j];
                    string key = field.Name;

                    if (!record.ContainsKey(key)) continue;

                    try {
                        field.SetValue(one, Parser.As(record[key], field.FieldType));
                    } catch {
                        Logger.Error("CsvFile.Load error at {0}:{1}. {2} is not {3} or Type NOT supported!!!", file, i + csv.start, key, field.FieldType);
                    }
                }

                ret.Add(one);
            }

            return ret;
        }

        /// <summary>
        ///		Load data and store it into dictionary.
        /// </summary>
        /// <typeparam name="K">Key type</typeparam>
        /// <typeparam name="T">Value type</typeparam>
        /// <param name="file">Path of CSV file. Relative to Assets/_Game/Setting/.</param>
        /// <param name="key">Name of key in header.</param>
        /// <param name="header">Header line. Data before header line will be ignored.</param>
        /// <returns>Data stored in dictionary.</returns>
        public static Dictionary<K, T> Load<K, T>(string file, string key, int header = 0) where T : new() {
            Dictionary<K, T> ret = new Dictionary<K, T>();
            CsvFile csv = new CsvFile();

            if (!csv.Parse(file, header)) return ret;

            Type type = typeof(T);
            FieldInfo[] fields = type.GetFields();

            for (int i = 0; i < csv.Count; ++i) {
                T one = new T();
                K id = default(K);
                Dictionary<string, string> record = csv.contents[i];

                if (!record.ContainsKey(key)) continue;

                try {
                    id = Parser.As<K>(record[key]);
                } catch {
                    Logger.Error("CsvFile.Load<K, T> error at {0}:{1}. Key {2} is not {3}", file, i + csv.start, key, typeof(K));
                    continue;
                }

                for (int j = 0; j < fields.Length; ++j) {
                    FieldInfo field = fields[j];
                    string name = field.Name;

                    if (!record.ContainsKey(name)) continue;

                    try {
                        field.SetValue(one, Parser.As(record[name], field.FieldType));
                    } catch {
                        Logger.Error("CsvFile.Load error at {0}:{1}. {2} is not {3} or Type NOT supported!!!", file, i + csv.start, key, field.FieldType);
                    }
                }

                ret.Add(id, one);
            }

            return ret;
        }

        /// <summary>
        ///		Simple parse a CSV file.
        /// </summary>
        /// <typeparam name="T">Data type.</typeparam>
        /// <param name="file">Path of CSV file. Relative to Assets/_Game/Setting/.</param>
        /// <param name="header">Header line. Data before header line will be ignored.</param>
        /// <returns>List of data</returns>
        public static CsvFile Load(string file, int header = 0) {
            CsvFile csv = new CsvFile();
            csv.Parse(file, header);
            return csv;
        }

        /// <summary>
        ///		Get number of valid record in this file.
        /// </summary>
        public int Count { get { return contents.Count; } }

        /// <summary>
        ///		Get title of this CSV file.
        /// </summary>
        public List<string> Keys { get { return keys; } }

        /// <summary>
        ///		Get attribute.
        /// </summary>
        /// <typeparam name="T">Attribute value type.</typeparam>
        /// <param name="index">Index of record in this file. The first line under header is 0.</param>
        /// <param name="key">Name of this attribute.</param>
        /// <param name="def">Default value if not configured.</param>
        /// <returns>Value of this attribute.</returns>
        public T Get<T>(int index, string key, T def = default(T)) {
            if (Count <= index || !contents[index].ContainsKey(key)) return def;

            try {
                return Parser.As<T>(contents[index][key]);
            } catch {
                Logger.Error("CsvFile.Get error at {0}:{1}. {2} is not {3} or Type NOT supported!!!", name, index + start, key, typeof(T));
                return def;
            }
        }

        private bool Parse(string file, int header) {
            keys = new List<string>();
            contents = new List<Dictionary<string, string>>();
            name = file;
            start = header + 2;

            TextAsset asset = Assets.Load<TextAsset>(file);
            if (asset == null) {
                Logger.Error("Can NOT load CSV file at {0}", file);
                return false;
            }

            string[] lines = asset.text.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
            string title = lines[header];

            if (string.IsNullOrEmpty(title)) {
                Logger.Error("Bad header line {0} in {1}", header, file);
                return false;
            }

            keys.AddRange(title.Split('\t'));
            char[] delim = new char[] { '\t' };

            for (int i = header + 1; i < lines.Length; ++i) {
                if (string.IsNullOrEmpty(lines[i].Trim())) continue;

                string[] line = lines[i].Split(delim);
                if (line.Length == 0) continue;

                Dictionary<string, string> record = new Dictionary<string, string>();
                for (int j = 0; j < line.Length && j < keys.Count; ++j) {
                    string key = keys[j];
                    string val = line[j];

                    if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(val)) continue;
                    record.Add(key, val);
                }
                contents.Add(record);
            }

            return true;
        }

        #region PRIVATES
        List<string> keys;
        List<Dictionary<string, string>> contents;
        string name;
        int start;
        #endregion
    }
}
